﻿namespace FitnessTracker.Observers
{
    using System;
    using FitnessTracker.Calculators;
    using FitnessTracker.Entities;
    using FitnessTracker.Packet;

    public class DataLogger : IObserver<HxMPacket>
    {
        private readonly DateTime logStartingPoint;
        private readonly int workoutID;
        private readonly FitnessTrackerEntities fitnessTrackerEntities = new FitnessTrackerEntities();
        private readonly RollingAccumulator<double> currentDistanceCalculator;
        private readonly bool ignoreNonHeartBeatRelatedDataPoints;

        // Since the use of dynamic keyword in RollingAccumulator, forces conversion of arithmetic caluclations 
        // to long, long is passed as a type to the strides calculator, though a more appropriate type 
        // should be uint
        private readonly RollingAccumulator<uint> currentStridesNumberCalculator;

        public DataLogger(int currentWorkoutID)
        {
            this.logStartingPoint = DateTime.Now;
            this.workoutID = currentWorkoutID;
            this.ignoreNonHeartBeatRelatedDataPoints = true;
        }

        public DataLogger(
            int currentWorkoutID,
            double residualDistanceOffset,
            byte residualStridesOffset)
        {
            this.logStartingPoint = DateTime.Now;
            this.workoutID = currentWorkoutID;

            this.currentDistanceCalculator = new RollingAccumulator<double>(
                residualDistanceOffset,
                HxMPacketValueRanges.DistanceUpperRangeInMeters);
            this.currentStridesNumberCalculator = new RollingAccumulator<uint>(
                residualStridesOffset,
                HxMPacketValueRanges.StridesUpperRange);
        }
  
        public void OnNext(HxMPacket packet)
        {
            var workoutDataEvent = this.WriteNewDataEventToDatabase();
            CreateHeartRatePointDataPoint(packet, workoutDataEvent);
            CreateHeartRateIntervalDataPoints(packet, workoutDataEvent);
            if (!this.ignoreNonHeartBeatRelatedDataPoints)
            {
                CreateInstanteniousSpeedDataPoint(packet, workoutDataEvent);
                CreateDistanceDataPoint(packet, workoutDataEvent);
                CreateStridesDataPoint(packet, workoutDataEvent);
            }

            fitnessTrackerEntities.SaveChanges();
        }
        
        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnCompleted()
        {
            this.fitnessTrackerEntities.Dispose();
        }

        private WorkoutDataEvent WriteNewDataEventToDatabase()
        {
            var workoutDataEvent = new WorkoutDataEvent
            {
                workoutID = this.workoutID,
                eventTimeOffsetInSec = TimeOffsetCalculator.GetTimeDifferenceBetweenEventsInFractionOfSeconds(
                this.logStartingPoint,
                DateTime.Now)
            };
            this.fitnessTrackerEntities.WorkoutDataEvents.Add(workoutDataEvent);
            this.fitnessTrackerEntities.SaveChanges();
            return workoutDataEvent;
        }

        private void CreateHeartRatePointDataPoint(HxMPacket packet, WorkoutDataEvent workoutDataEvent)
        {
            this.fitnessTrackerEntities.WorkoutHeartRatePoints.Add(
                new WorkoutHeartRatePoint 
                { 
                    dataEventID = workoutDataEvent.eventID,
                    heartRate = packet.Payload.HeartRate 
                });
        }

        private void CreateInstanteniousSpeedDataPoint(HxMPacket packet, WorkoutDataEvent workoutDataEvent)
        {
            this.fitnessTrackerEntities.WorkoutInstantaneousSpeedPoints.Add(
                new WorkoutInstantaneousSpeedPoint
                {
                    dataEventID = workoutDataEvent.eventID,
                    instantaneousSpeedInMetersPerSecond = packet.Payload.InstantaneousSpeedInMetersPerSecond
                });
        }

        private void CreateHeartRateIntervalDataPoints(HxMPacket packet, WorkoutDataEvent workoutDataEvent)
        {
            foreach (
                var heartBeatTimeStamp in
                    HeartBeatIntervalCalculator.CalculateHeartBeatInterval(
                        packet.Payload.HeartBeatNumber, packet.Payload.HeartBeatTimeStamps))
            {
                this.fitnessTrackerEntities.WorkoutHeartBeatIntervalPoints.Add(
                    new WorkoutHeartBeatIntervalPoint
                        {
                            dataEeventID = workoutDataEvent.eventID,
                            heartBeatIntervalInMs = heartBeatTimeStamp
                        });
            }
        }

        private void CreateStridesDataPoint(HxMPacket packet, WorkoutDataEvent workoutDataEvent)
        {
            this.fitnessTrackerEntities.WorkoutStridesPoints.Add(
                new WorkoutStridesPoint
                {
                    dataEventID = workoutDataEvent.eventID,
                    numberOfStrides = (int)this.currentStridesNumberCalculator.Calculate(packet.Payload.Strides)
                });
        }

        private void CreateDistanceDataPoint(HxMPacket packet, WorkoutDataEvent workoutDataEvent)
        {
            this.fitnessTrackerEntities.WorkoutDistancePoints.Add(
                new WorkoutDistancePoint
                {
                    dataEventID = workoutDataEvent.eventID,
                    distanceInMeters = this.currentDistanceCalculator.Calculate(packet.Payload.DistanceInMeters)
                });
        }
    }
}
